package org.mobicents.media.server.impl.resource.audio.soundcard; import java.io.IOException; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.DataLine; import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.SourceDataLine; import javax.sound.sampled.AudioFormat.Encoding; import org.apache.log4j.Logger; import org.mobicents.media.Buffer; import org.mobicents.media.Format; import org.mobicents.media.format.AudioFormat; import org.mobicents.media.server.impl.AbstractSink; import org.mobicents.media.server.impl.rtp.sdp.AVProfile; import org.mobicents.media.server.spi.dsp.Codec; import org.mobicents.media.server.spi.events.NotifyEvent; import org.mobicents.media.server.spi.resource.Player; import org.xiph.speex.spi.SpeexEncoding; /** * PlayerImpl is to play audio to hardware * * @author amit bhayani * */ public class PlayerImpl extends AbstractSink implements Player { private static final Logger logger = Logger.getLogger(PlayerImpl.class); private final static Format[] FORMATS = new Format[] { AVProfile.L16_MONO, AVProfile.L16_STEREO, Codec.LINEAR_AUDIO }; private final static Encoding GSM_ENCODING = new Encoding("GSM0610"); private volatile boolean first = true; boolean bigEndian = false; private SourceDataLine sourceDataLine = null; private boolean isAcceptable = false; private Format fmt; private javax.sound.sampled.AudioFormat audioFormat = null; public PlayerImpl(String name) { super(name); } private javax.sound.sampled.AudioFormat.Encoding getEncoding(String encodingName) { if (encodingName.equalsIgnoreCase(AudioFormat.ALAW)) { return javax.sound.sampled.AudioFormat.Encoding.ALAW; } else if (encodingName.equalsIgnoreCase(AudioFormat.ULAW)) { return javax.sound.sampled.AudioFormat.Encoding.ULAW; } else if (encodingName.equalsIgnoreCase(AudioFormat.SPEEX)) { return SpeexEncoding.SPEEX; } else if (encodingName.equalsIgnoreCase(AudioFormat.GSM)) { return GSM_ENCODING; } else { return javax.sound.sampled.AudioFormat.Encoding.PCM_SIGNED; } } @Override public void onMediaTransfer(Buffer buffer) throws IOException { if (first) { first = false; AudioFormat fmt = (AudioFormat) buffer.getFormat(); float sampleRate = (float) fmt.getSampleRate(); int sampleSizeInBits = fmt.getSampleSizeInBits(); int channels = fmt.getChannels(); int frameSize = (fmt.getFrameSizeInBits() / 8); float frameRate = (float) fmt.getFrameRate(); boolean bigEndian = fmt.getEndian() == 1; Encoding encoding = getEncoding(fmt.getEncoding()); frameSize = (channels == AudioSystem.NOT_SPECIFIED || sampleSizeInBits == AudioSystem.NOT_SPECIFIED) ? AudioSystem.NOT_SPECIFIED : ((sampleSizeInBits + 7) / 8) * channels; audioFormat = new javax.sound.sampled.AudioFormat(encoding, sampleRate, sampleSizeInBits, channels, frameSize, sampleRate, bigEndian); // FIXME : Need a configuration to select the specific hardware DataLine.Info dataLineInfo = new DataLine.Info(SourceDataLine.class, audioFormat); // TODO : Should getting the SourceDataLine go in start() In which case its configurable to know the Formats // beforehand. try { sourceDataLine = (SourceDataLine) AudioSystem.getLine(dataLineInfo); sourceDataLine.open(audioFormat); sourceDataLine.start(); } catch (LineUnavailableException e) { logger.error(e); this.failed(NotifyEvent.RX_FAILED, e); this.stop(); } catch (IllegalArgumentException e) { logger.error(e); this.failed(NotifyEvent.RX_FAILED, e); this.stop(); } } // FIXME : write() will block till all bytes are written. Need async operation here. try { sourceDataLine.write((byte[]) buffer.getData(), buffer.getOffset(), buffer.getLength()); } catch (IllegalArgumentException e) { logger.error(e); } catch (ArrayIndexOutOfBoundsException e) { logger.error(e); } } public Format[] getFormats() { return FORMATS; } public boolean isAcceptable(Format format) { if (fmt != null && fmt.matches(format)) { return isAcceptable; } for (int i = 0; i < FORMATS.length; i++) { if (FORMATS[i].matches(format)) { fmt = format; isAcceptable = true; return isAcceptable; } } isAcceptable = false; return isAcceptable; } @Override public void stop() { super.stop(); first = false; if (sourceDataLine != null) { sourceDataLine.drain(); sourceDataLine.close(); } } @Override public void start() { super.start(); first = true; } }